﻿--[[
	This file is part of FlexBar3.

	FlexBar3 is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	FlexBar3 is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with FlexBar3.  If not, see <http://www.gnu.org/licenses/>.
]]
-- This module takes care of actualy creating the buttons.
local Button = FlexBar3:NewModule("Button");

-- No dependencies
Button.Dependencies = nil;

-- Table to store all buttonclasses in the order they have to be loaded
local ClassList = {};
Button.ClassList = ClassList;

-- Local to store the definitive buttonclass (we find out what it is in Enable)
local ButtonClass;
function Button:Enable()
	FlexBar3:Debug("Button enabled");
	-- Find out what shapeshifts there are and add them to the state list
	self.States = {};
	self.TypeOrder = {};
	self.StateButtonCache = {};
	-- Note: at this point no buttons exist, so this UpdateStates call's effect will be limited to generating the statedriver without generating any statebuttons
	self:UpdateStates();
	
	-- Create the actual buttons
	ButtonClass = FlexBar3.OO.Class(unpack(ClassList));
	Button.ButtonClass = ButtonClass;
	if(not Button.DB.ButtonList) then
		FlexBar3:Debug("Initializing an empty buttonlist");
		Button.DB.ButtonList = {};
	else
		for Name, Settings in pairs(Button.DB.ButtonList) do
			self:CreateButton(Name);
		end
	end
	-- Update the ButtonClass revisions in the DB
	if(not Button.DB.ButtonClasses) then
		Button.DB.ButtonClasses = {};
	end
	local ButtonClasses = Button.DB.ButtonClasses;
	for i = 1, #ClassList do
		local CurrentClass = ClassList[i];
		ButtonClasses[CurrentClass.Name] = CurrentClass.Revision;
	end
end

function Button:Disable()
	
end

function Button:NewButtonClass(Name, Revision, Class)
	assert(type(Name) == "string", ("Name must be a string, it was a %s"):format(type(Name)), 2);
	assert(type(Revision) == "number", ("Revision must be a number, it was a %s"):format(type(Revision)), 2);
	assert(ButtonClass == nil, "ButtonClasses cannot be instanciated after load time", 2);
	Class = Class or FlexBar3.OO.Class();
	Class.Name = Name;
	Class.Revision = Revision;
	tinsert(ClassList, Class);
	return Class;
end

-- List of named buttons
local ButtonList = {};
Button.ButtonList = ButtonList;

-- List of unnamed buttons
local UnnamedButtonList = {};
Button.UnnamedButtonList = UnnamedButtonList;

-- List of unnused buttons waiting to be reused
local ButtonCache = {};
Button.ButtonCache = ButtonCache;

-- Passing a name is optional, if none is passed the button is unnamed and cannot save data between sessions
function Button:CreateButton(Name)
	assert(Name == nil or type(Name) == "string", ("Modules.Button.CreateButton: A name was passed, but it was a [%s] instead of a string"):format(type(Name)));
	local Button = self:GetButtonFromCache(Name) or ButtonClass:new(Name);
	if(Name) then
		ButtonList[Name] = Button;
	else
		tinsert(UnnamedButtonList, Button);
	end
end

function Button:RemoveButton(Name)
	local Button;
	if(type(Name) == "string") then
		assert(ButtonList[Name], ("Modules.Button.RemoveButton: Cannot remove non-existant button [%s]"):format(Name));
		Button = ButtonList[Name];
		ButtonList[Name] = nil;
	elseif(type(Name) == "table") then
		Button = Name;
		Name = Button.Name;
		-- The button is named, make sure it exists and then proceed.
		if(type(Name) == "string") then
			assert(ButtonList[Name], ("Modules.Button.RemoveButton: Cannot remove non-existant button [%s]"):format(Name));
			ButtonList[Name] = nil;
		-- The button is unnamed, find out its index
		elseif(Name == false) then
			local Continue = false;
			for i = 1, #UnnamedButtonList do
		
		if(UnnamedButtonList[i] == Button) then
					UnnamedButtonList[i] = nil;
					Continue = true;
				end
			end
			assert(Continue, "Modules.Button.RemoveButton: The passed table was not a button.");
		end
	else
		error(("Modules.Button.RemoveButton: Expected a string or table as argument, got a [%s]"):format(type(Name)));
	end
	Button:Disable();
	self.DB.ButtonList[Name] = nil;
	tinsert(ButtonCache, Button);
end

function Button:GetButton(Name)
	return self.ButtonList[Name];
end

function Button:GetButtonFromCache(Name)
	local Button = tremove(ButtonCache);
	if(Button) then
		Button:Enable(Name);
	end
	return Button or false;
end

-- Attribute stuff starts here

-- The state driver frame is the same for all buttons.
local SecureStateDriverFrame = CreateFrame("Frame", nil, UIParent, "SecureStateDriverTemplate");
SecureStateDriverFrame:SetAttribute("statemap-state", "$input");
Button.SecureStateDriverFrame = SecureStateDriverFrame;
SecureStateDriverFrame:SetPoint("BOTTOMLEFT", UIparent, "BOTTOMLEFT", 0, 0);
SecureStateDriverFrame:Show();

-- This function gets called whenever the state of the driver changes
function SecureStateDriverFrame:StateChanged(NewState)
	self.State = NewState;
	-- Trigger a callback for every button when the state changes
	for _, CurrentButton in pairs(ButtonList) do
		CurrentButton:TriggerCallback("StateChanged");
	end
end

function SecureStateDriverFrame:GetModifiedState()
	return SecureButton_GetModifierPrefix() .. GetUnmodifiedState();
end

function SecureStateDriverFrame:GetUnmodifiedState()
	return "%s-" .. SecureStateDriverFrame.State;
end

function Button:AddState(Type, Name, Params, Absolute)
	if(not self.States[Type]) then
		self.States[Type] = {};
		tinsert(self.TypeOrder, Type);
	end
	if(self.States[Type][Name]) then return false; end
	tinsert(self.States[Type], {Name, Params, Absolute});
end

function Button:RemoveState(Type, Name)
	if(Type and self.States[Type]) then
		if(Name) then
			for Key, StateTable in ipairs(self.States[Type]) do
				if(StateTable[1] == Name) then
					tremove(self.States[Type], Key);
					return true;
				end
			end
			return false;
		else
			RemoveByValue(self.TypeOrder, Type);
			self.States[Type] = nil;
		end
	end
end

function Button:UpdateStates()
	FlexBar3:Debug("Updating state driver");
	self:RemoveState("shapeshift");
	self:RemoveState("target");
	local _, Class = UnitClass("player");
	-- Add shapeshift forms to the current states
	for State = 1, GetNumShapeshiftForms() do
		local _, FormName = GetShapeshiftFormInfo(State);
		local FormLink = GetSpellLink(FormName);
		local FormID = FormLink:match("Hspell:(%d+)", 1);
		if(FormID == "768") then --768 is catform
			self:AddState("shapeshift", "Stealth", "stance:" .. State .. ",stealth:1", true);
		end
		self:AddState("shapeshift", ("Shape%d"):format(State), "stance:" .. State, true);
	end
	-- Specific shadowform support code as its not seen as a shapeshift
	if(Class == "PRIEST") then
		self:AddState("shapeshift", "ShadowForm", "stance:1", true);
	end
	self:AddState("shapeshift", "Unshifted", "", true);
	-- Add friendly/hostile/no target remaps
	self:AddState("target", "harm", "harm", false);
	self:AddState("target", "help","help", false);
	self:AddState("target", "none", "", false);
	self:GenerateStateDriver();
	self:GenerateStateButton();
end

function Button:GenerateStateDriver(Save)
	FlexBar3:Debug("Generating state driver");
	local StateDescriptor = {};
	local StateList = {};
	local StateDriver = "";
	-- Help nested loops conquer the universe !
	local TypeOrder = self.TypeOrder;
	for MainKey = 1, #TypeOrder do
		local Type = TypeOrder[MainKey];
		local States = self.States[Type];
		local CurrStateDescriptor = {};
		for SubKey = 1, #States do
			local StateTable = States[SubKey];
			local State = StateTable[1];
			local Params = StateTable[2];
			-- Consider old states and add to them
			for Key = 1, #StateDescriptor do
				local SubStateTable = StateDescriptor[Key];
				local PrecStateName = SubStateTable[1];
				local PrecStateParams = SubStateTable[2];
				if(Params ~= "" and PrecStateParams ~= "") then
					tinsert(CurrStateDescriptor, {PrecStateName .. "-" .. State, PrecStateParams .. "," .. Params});
				elseif(PrecStateParams == "" and Params ~= "") then
					tinsert(CurrStateDescriptor, {PrecStateName .. "-" .. State, Params});
				else
					tinsert(CurrStateDescriptor, {PrecStateName .. "-" .. State, PrecStateParams});
				end
				-- Get rid of the old state in favor of the new one
				tinsert(CurrStateDescriptor, {PrecStateName, false});
			end
			-- If this is the first time we loop, then we can add the "pure" state name into the list
			if(MainKey == 1) then tinsert(CurrStateDescriptor, {State, Params}); end
		end
		-- Merge the CurrStateDescriptor into the main one
		for Key = 1, #CurrStateDescriptor do

			local CurrStateTable = CurrStateDescriptor[Key];
			local State = CurrStateTable[1];
			local Params = CurrStateTable[2];
			if(Params) then
				tinsert(StateDescriptor, {State, Params});
			else
				for Key = 1, #StateDescriptor do
					local StateTable = StateDescriptor[Key];
					if(StateTable[1] == State) then
						tremove(StateDescriptor, Key);
						break;
					end
				end
			end
		end
	end
	for Key = 1, #StateDescriptor do
		local StateTable = StateDescriptor[Key];
		local State = StateTable[1];
		tinsert(StateList, State);
		local Params = StateTable[2];
		if(Params) then
			if(Params ~= "") then
				StateDriver = StateDriver .. "[" .. Params .. "]" .. State .. ";";
			else
				StateDriver = StateDriver .. State .. ";";
			end
		end
	end
	if(Save ~= false) then
		self.StateDriver = StateDriver;
		self.StateList = StateList;
		RegisterStateDriver(SecureStateDriverFrame, "state", StateDriver);
	end
	return StateDriver;
end

function Button:GenerateStateButton()
	-- Clear cache before regenerating
	local StateButtonCache = self.StateButtonCache;
	for Key in pairs(StateButtonCache) do
		StateButtonCache[Key] = nil;
	end
	for _, CurrentButton in pairs(ButtonList) do
		FlexBar3:Debug("Generating state button for ", CurrentButton.Name);
		CurrentButton:GenerateStateButton();
	end
end
